// SPDX-FileCopyrightText: Copyright 2023 citrus Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <jni.h>
#include <android/hardware_buffer.h>
#include <android/hardware_buffer_jni.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <unistd.h>
#include <cstdlib>
#include "android_protocol.h"

namespace jni {

    static jobject JNICALL recvHardwareBuffer(JNIEnv *env, jclass clazz, jint fd) {
        AHardwareBuffer *hardwareBuffer = nullptr;
        AHardwareBuffer_recvHandleFromUnixSocket(fd, &hardwareBuffer);
        jobject ret = AHardwareBuffer_toHardwareBuffer(env, hardwareBuffer);
        AHardwareBuffer_release(hardwareBuffer);
        return ret;
    }

    static jint JNICALL recvFileDescriptor(JNIEnv *env, jclass clazz, jint fd) {
        return ::recvFileDescriptor(fd);
    }

    static void JNICALL sendFileDescriptor(JNIEnv *env, jclass clazz, jint fd, jint value) {
        return ::sendFileDescriptor(fd, value);
    }

    static jint JNICALL recvMessage(JNIEnv *env, jclass clazz, jint fd, jobject parcel) {
        return ::recvMessage(fd, AParcel_fromJavaParcel(env, parcel));
    }

    static void JNICALL sendMessage(JNIEnv *env, jclass clazz, jint fd, jint num, jobject parcel) {
        return ::sendMessage(fd, num, AParcel_fromJavaParcel(env, parcel));
    }
}

static int createBackend() {
    static const char* key = "CITRUS_BACKEND_ID";
    const char* str = getenv(key);
    if (!str || *str < '0' || *str > '9') return 0;
    int fd = 0, socket[2];
    while (*str >= '0' && *str <= '9')
        fd = fd * 10 - '0' + *str++;
    unsetenv(key);
    socketpair(AF_UNIX, SOCK_STREAM, 0, socket);
    if (jint pid = fork()) {
        dup2(socket[0], fd);
        close(socket[0]);
        close(socket[1]);
        return pid;
    }

    /* backend process */
    char *argv = nullptr;
    close(socket[0]);
    if (socket[1] != fileDescriptorFd) {
        dup2(socket[1], fileDescriptorFd);
        close(socket[1]);
    }
    for (int i = fileDescriptorFd + 1; i < socket[1] || i <= fd; ++i) {
        int flag = fcntl(fd, F_GETFL);
        if (!(flag & FD_CLOEXEC))
            fcntl(fd, F_SETFL, flag | FD_CLOEXEC);
    }
    return fexecve(fd, &argv, environ);
}

extern "C" jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
    static const JNINativeMethod methods[] =
            {
                    {"nativeRecvHardwareBuffer", "(I)Landroid/hardware/HardwareBuffer;", (void *) &jni::recvHardwareBuffer},
                    {"nativeRecvFileDescriptor", "(I)I",                                 (void *) &jni::recvFileDescriptor},
                    {"nativeSendFileDescriptor", "(II)V",                                (void *) &jni::sendFileDescriptor},
                    {"nativeRecvMessage",        "(ILandroid/os/Parcel;)I",              (void *) &jni::recvMessage},
                    {"nativeSendMessage",        "(IILandroid/os/Parcel;)V",             (void *) &jni::sendMessage},
            };
    signal(SIGPIPE, SIG_IGN);
    if (!createBackend()) return JNI_ERR;
    JNIEnv *env;
    if (jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK)
        return JNI_ERR;
    jclass clazz = env->FindClass("com/fanwj/citrus/JniLibrary");
    if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(*methods)) < 0)
        return JNI_ERR;
    return JNI_VERSION_1_6;
}